using System;
using System.Collections.Generic;
using System.Text;
using Adoor.Object.Query;
using Adoor.Object.Entity;

namespace Adoor.Object.Domain
{
    [Serializable,System.ComponentModel.ToolboxItem(true), System.ComponentModel.DesignTimeVisible(true)]     
    public abstract class ObjectResolverBase : System.ComponentModel.Component, IObjectResolver
    {
        public abstract ICollection<ObjectDomain> ManagedDomains { get; }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public ObjectPath GetPath(Type type)
        {
            return GetPath(type, type.Name);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="alias"></param>
        /// <returns></returns>
        public ObjectPath GetPath(Type type, string alias)
        {
            lock (this)
            {
                EntityData factory = this.FindEntityData(type);
                if (factory == null)
                    throw new Exception("entity info not found for type " + type.FullName);
                return factory.GetPath(alias);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public ObjectPath GetPath(string name)
        {
            return GetPath(name, name);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="alias"></param>
        /// <returns></returns>
        public ObjectPath GetPath(string name, string alias)
        {
            lock (this)
            {
                EntityData factory = this.FindEntityData(name);
                if (factory == null)
                    throw new Exception("entity info not found for type " + name);
                return factory.GetPath(alias);
            }
        }

        /// <summary>
        /// Create a new <see cref="ObjectQuery"/> from given text
        /// </summary>
        /// <param name="text">query text</param>
        /// <returns>a new <see cref="ObjectQuery"/> instance</returns>
        public ObjectQuery ParseQuery(string text)
        {
            return new ObjectQuery(this, text);
        }

        /// <summary>
        /// Create a new <see cref="ObjectPath"/> from given text
        /// </summary>
        /// <param name="text">query text</param>
        /// <returns>a new <see cref="ObjectPath"/> instance</returns>
        public ObjectPath ParsePath(string text)
        {
            return ObjectPath.Parse(this, text);
        }

        /// <summary>
        /// Retreive an entity external type from internal meta info.
        /// </summary>
        /// <remarks>When the given object is not an entity this methods returns the real type of the object</remarks> 
        /// <param name="entity"></param>
        /// <returns></returns>
        public Type GetType(object entity)
        {
            if (entity is IEntity)
            {
                return ((IEntity)entity).State.EntityFactory.EntityData.EntityType;
            }
            else
                return entity.GetType();
        }


        /// <summary>
        /// Retreive an <see cref="EntityData"/> instance (meta info) from entity name.
        /// </summary>
        /// <remarks>this methods stress an ADOOR limitation : we can not map twice 
        /// the same model on two different domains</remarks> 
        /// <param name="name">enntity name</param>
        /// <returns></returns>
        public EntityData FindEntityData(string name)
        {
            foreach (ObjectDomain domain in ManagedDomains)
            {
                EntityData entityData = domain.EntityInfos[name];
                if (entityData != null)
                    return entityData;
            }
            return null;
        }

        /// <summary>
        /// Retreive an <see cref="EntityData"/> instance (meta info) from entity type.
        /// </summary>
        /// <remarks>this methods stress an ADOOR limitation : we can not map twice 
        /// the same model on two different domains</remarks>
        /// <param name="type">entity type</param>
        /// <returns></returns>
        public EntityData FindEntityData(Type type)
        {
            foreach (ObjectDomain domain in ManagedDomains)
            {
                EntityData entityData = domain.EntityInfos[type];
                if (entityData != null)
                    return entityData;
            }
            return null;
        }

        /// <summary>
        /// Retreive an <see cref="IEntityFactory"/> instance from entity type.
        /// This result can be used to manage entities life cycle.
        /// </summary>
        /// <param name="type">entity type</param>
        /// <returns></returns>
        public IEntityFactory FindFactory(Type type)
        {
            EntityData entityData = FindEntityData(type);
            return FindFactory(entityData, type.FullName);  
        }


        /// <summary>
        /// Retreive an <see cref="IEntityFactory"/> instance from entity type.
        /// This result can be used to manage entities life cycle.
        /// </summary>
        /// <param name="name">entity name</param>
        /// <returns></returns>
        public IEntityFactory FindFactory(string name)
        {
            EntityData entityData = FindEntityData(name);
            return FindFactory(entityData,name);  
           
        }

        private IEntityFactory FindFactory(EntityData entityData, string typeName)
        {
            if (entityData == null)
                throw new Exception("EntityData not found for type " + typeName);
            IEntityFactory factory = (IEntityFactory)entityData.GetService(typeof(IEntityFactory));
            if (factory == null)
                throw new Exception("factory not found for type " + typeName);
            return factory;
        }

    }
}
